/*
* Copyright (C) 2008-2009 J-P Nurmi jpnurmi@gmail.com
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
* License for more details.
*
* $Id: ircsession_p.cpp 581 2009-05-19 16:40:38Z jpnurmi $
*/

#include "ircsession_p.h"
#include "ircsession.h"
#include "ircchannelbuffer.h"
#include "ircserverbuffer.h"
#include "ircbuffer.h"
#include "ircbuffer_p.h"
#include "irc.h"

namespace Irc
{
    static QList<QByteArray> splitByteArray(const QByteArray& ba, char sep, int limit)
    {
        QList<QByteArray> list;
        int start = 0;
        int end = 0;
        while ((end = ba.indexOf(sep, start)) != -1 && list.count() < limit - 1) {
            list.append(ba.mid(start, end - start));
            start = end + 1;
        }
        list.append(ba.mid(start));
        return list;
    }

    SessionPrivate::SessionPrivate() :
        q_ptr(0),
        socket(0),
        host(),
        port(6667),
        encoding(),
        buffer(),
        prevBuffer(0)
    {
    }

    void SessionPrivate::_q_connected()
    {
        qDebug() << "SessionPrivate::_q_connected";
        Q_ASSERT(serverBuffer);

        if (!serverBuffer->password().isEmpty())
            socket->write("PASS " + serverBuffer->password() + "\r\n");
        socket->write("NICK " + serverBuffer->nick() + "\r\n");

		// RFC 1459 states that "hostname and servername are normally
        // ignored by the IRC server when the USER command comes from
        // a directly connected client (for security reasons)", therefore
        // we don't need them.
        socket->write("USER " + serverBuffer->ident() + " unknown unknown" 
                      " :" + serverBuffer->realName() + "\r\n");
    }

    void SessionPrivate::_q_disconnected()
    {
        qDebug() << "SessionPrivate::_q_disconnected";
    }

    void SessionPrivate::_q_error()
    {
        qDebug() << "SessionPrivate::_q_error" << socket->error();
    }

    void SessionPrivate::_q_state()
    {
        qDebug() << "SessionPrivate::_q_state" << socket->state();
    }

    void SessionPrivate::_q_readData()
    {
        qint64 bytes = buffer.write(socket->readAll());
        buffer.seek(buffer.pos() - bytes);
        while (buffer.canReadLine())
        {
            QByteArray line = buffer.readLine();
            if (!line.isEmpty())
                processLine(line);
        }
    }

    void SessionPrivate::processLine(const QByteArray& data)
    {
        Q_Q(Session);

        // From RFC 1459:
        //  <message>  ::= [':' <prefix> <SPACE> ] <command> <params> <crlf>
        //  <prefix>   ::= <servername> | <nick> [ '!' <user> ] [ '@' <host> ]
        //  <command>  ::= <letter> { <letter> } | <number> <number> <number>
        //  <SPACE>    ::= ' ' { ' ' }
        //  <params>   ::= <SPACE> [ ':' <trailing> | <middle> <params> ]
        //  <middle>   ::= <Any *non-empty* sequence of octets not including SPACE
        //                 or NUL or CR or LF, the first of which may not be ':'>
        //  <trailing> ::= <Any, possibly *empty*, sequence of octets not including
        //                   NUL or CR or LF>

        //qDebug() << "DATA:" << data << data.toHex();
        if (!data.startsWith(':'))
        {
            if (prevBuffer)
            {
                // TODO:
                //qDebug() << "UNKNOWN";
                //prevBuffer->d_func()->prepare(prefix, command, code);
                prevBuffer->write(data);
            }
            return;
        }

        QByteArray line(data);
        line.replace("  ", 2, " ", 1);
        // no need to split the whole line which could be long
        // we need only the first few words
        QList<QByteArray> words = splitByteArray(line, ' ', 5);

        QByteArray prefix = words.value(0);
        prefix.remove(0, 1); // ':'
        for (int i = 0; i < prefix.count(); ++i)
            if (prefix.at(i) == '!' || prefix.at(i) == '@')
                prefix.truncate(i);

        QByteArray command = words.value(1);

        // handle PING/PONG
        if (!qstrcmp(command, "PING"))
        {
            QByteArray param = words.value(2);
            qDebug() << "PING:" << command << param << words;
            socket->write("PONG " + param);
            return;
        }

        bool numeric = false;
        int code = command.toInt(&numeric);
        if (numeric)
        {
            // TODO
            // ":irc.elisa.fi 001 jpnurmi :Welcome to the Internet Relay Network jpnurmi!jpnurmi@xob.kapsi.fi" 
            // ":irc.elisa.fi 002 jpnurmi :Your host is irc.elisa.fi, running version 2.11.2p1" 
            // ":irc.elisa.fi 003 jpnurmi :This server was created Wed Aug 13 2008 at 16:34:10 EEST" 
            qDebug() << "NUMERIC:" << prefix << command << code;

            if (code == Rfc::RPL_ENDOFMOTD || code == Rfc::ERR_NOMOTD)
            {
                foreach (Buffer* buffer, buffers)
                {
                    if (ChannelBuffer* channel = qobject_cast<ChannelBuffer*>(buffer))
                        socket->write("JOIN " + channel->name() + " " + channel->key() + "\r\n");
                }
            }

            switch (code)
            {
            case Irc::Rfc::RPL_NAMREPLY:
                {
                    QByteArray channel = words.value(4);
                    prevBuffer = buffers.value(channel.toLower());
                    if (prevBuffer)
                    {
                        prevBuffer->d_func()->prepare(prefix, command, code);
                        prevBuffer->write(data);
                    }
                }
                return;

            case Irc::Rfc::RPL_ENDOFNAMES:
                break;

            default:
                break;
            }
        }
        else
        {
            // TODO: KILL
            static const QList<QByteArray> targeted = QList<QByteArray>() 
                << "JOIN" << "KICK" << "MODE" << "NOTICE" << "PART" << "PRIVMSG" << "TOPIC";

            if (qBinaryFind(targeted, command) != targeted.constEnd())
            {
                QByteArray target = words.value(2);
                if (target.startsWith(':'))
                    target.remove(0, 1);

                prevBuffer = buffers.value(target.toLower());
                if (!prevBuffer)
                {
                    if (target.at(0) == '#' || target.at(0) == '&')
                        prevBuffer = new ChannelBuffer(target, QByteArray(), q);
                    else
                        prevBuffer = new Buffer(target, q);
                    buffers[target.toLower()] = prevBuffer;
                    emit q->bufferAvailable(prevBuffer);
                }
                prevBuffer->d_func()->prepare(prefix, command, code);
                prevBuffer->write(data);
            }
            else
            {
                // TODO
                // ":jaxr!jpnurmi@xob.kapsi.fi NICK :jaber" 
                // ":jaber!jpnurmi@xob.kapsi.fi INVITE jpnurmi :#channel" 
                // ":jaber!jpnurmi@xob.kapsi.fi QUIT :"leaving""
                qDebug() << "TODO:" << line;
            }
        }
    }
}
